using System;
using System.CodeDom;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using AutoUnitTest.Common.CodeGeneration;
using AutoUnitTest.Common.CodeGeneration.CS;
using System.Reflection;

namespace AutoUnitTest.Common.CodeAdapter
{
    public class GraphToCodeConvertor
    {
        private readonly Graph _graph;

        public GraphToCodeConvertor(Graph graph)
        {
            _graph = graph;
        }

        public string GetGeneratedClass(string fullClassName, IClassGenerator classGenerator)
        {
            string @class = fullClassName.Type();
            string @namespace = fullClassName.NameSpace();
            classGenerator.CreateClass(@class, @namespace);

            var methods = new CodeTypeMemberCollection();

            Vertex classVertex = _graph.VertexList.Find(x => 
                x.Name == fullClassName
                || x.Name.Contains(fullClassName)
                );


            foreach (Edge item in classVertex.IncomingEdges.Where(e => e.Type.Name == "m"))
            {
                Vertex methodVertex = item.StartVertex;

                MemberAttributes access = MethodAccess(methodVertex);

                if (access != 0) // generate only non-private
                {
                    var method = new CodeMemberMethod {Name = methodVertex.Name.Type()};

                    var parametrs = MethodParameters(methodVertex, method);

                    method.Attributes = access;
                    methods.Add(method);

                    Type returnedType = MethodReturns(methodVertex);


                    if (!returnedType.IsPrimitive && returnedType != typeof(void))
                        method.Statements.Add(new CodeMethodReturnStatement(
                                                  new CodeObjectCreateExpression(returnedType)));
                    else if (returnedType.IsPrimitive)
                    {
                        object defVal = Activator.CreateInstance(returnedType);
                        method.Statements.Add(new CodeMethodReturnStatement(
                                                  new CodePrimitiveExpression(defVal)));
                    }

                    classGenerator.AddMethod(method.Name,
                        new MethodParameter{Name = "", Type = returnedType},
                        access,
                        parametrs.ToArray()
                        );
                }
            }

            return classGenerator.GetCodeResult();
        }

        private Type MethodReturns(Vertex methodVertex)
        {
            var ret = new MethodParameter();

            var returnTypeEdge = methodVertex.IncomingEdges.Find(e => e.Type.Name == "t");

            ret.Type = Type.GetType(returnTypeEdge.StartVertex.Name);


            //method.ReturnType = new CodeTypeReference(returnType);

            return ret.Type;
        }

        private List<MethodParameter> MethodParameters(Vertex methodVertex, CodeMemberMethod method)
        {
            var ret = new List<MethodParameter>();

            var paramEdges = methodVertex.IncomingEdges.Where(e => e.Type.Name == "p");

            //method.Attributes = access;
          //  method.Name = methodVertex.Name.Type();

            foreach (Edge edge in paramEdges)
            {
                var argNameVertex = edge.StartVertex;

                var argTypeVertex = argNameVertex.IncomingEdges
                    .Find(e => e.Type.Name == "t")
                    .StartVertex;

                var paramType = Type.GetType(argTypeVertex.Name);

                if (paramType != null)
                    ret.Add(new MethodParameter {Type = paramType, Name = argNameVertex.Name});
                else
                    ret.Add(new MethodParameter { TypeAsString = argTypeVertex.Name, 
                        Name = argNameVertex.Name });
            }
            return ret;
        }

        private MemberAttributes MethodAccess(Vertex methodVertex)
        {
            var accessEdge = methodVertex.IncomingEdges.Find(e => e.Type.Name == "L");
            switch (accessEdge.StartVertex.Name)
            {
                case "public": return MemberAttributes.Public | MemberAttributes.Final;
                case "protected": return MemberAttributes.Family | MemberAttributes.Final;
                case "internal": return MemberAttributes.Assembly | MemberAttributes.Final;
                default: return 0;
            }
        }
    }
}
